// 为什么开发需要模块化 (防止开发的时候 有命名冲突问题)  解决方案， 采用对象封装的方式（命名空间）
// 函数作用域，来解决模块化问题。

// 早起的规范 cmd seajs  amd requirejs  umd  (commonjs规范node中发明的)  （es6 module）
// iife 立即执行函数  systemjs模块规范。。。

// 模块化的好处： 可以让代码方便复用，可以得到更好的扩展
// 解决命名冲突问题，隔离代码
// node中的commonjs规范

// 通过这个规范来定义、使用、导出模块
// 1.每个js文件都是一个模块 (每个文件在运行的时候都会产生一个函数)
// 2.每次使用一个模块可以采用 require 方式来进行导入
// 3.如果想让别人使用当前模块可以采用 module.exports 方式

// 在node中模块分成三类， 内置模块/核心模块   第三方库   文件模块

// 内置模块  fs  path
// 同步（性能高）、异步 （运行的过程中）
const fs = require("fs");
const path = require("path");
const vm = require("vm");

// 在commonjs规范中 文件模块会默认提供两个参数 __dirname __filename
// 在操作资源的时候同意采用绝对路径，相对路径会根据运行的位置不同发生错误

// __filename 文件的绝对路径 ，当前的文件的路径不能修改
// __dirname 文件所在的目录，绝对路径 不能修改的

// console.log(path.join(__dirname, "./name.txt", "/"));
// console.log(path.resolve(__dirname, "./name.txt", "/")); // resolve 默认根据代码执行的位置来进行解析
// fs.readFileSync();

// 如果路径有/的话，只能采用join,resolve 会回到跟路径
// console.log(path.dirname(__filename)); // __dirname
// console.log(path.extname("a.min.js")); // 取最后的扩展名
// console.log(path.delimiter); // 路径分隔符
// console.log(path.win32.delimiter);

// let content = fs.readFileSync(path.resolve(__dirname, "name.txt"), "utf-8");
// console.log(content);

// let value = require("./module");
// console.log(value);

// 如何让一个字符串能执行？
// eval 执行不会创造隔离，而且性能差
// new Function 在node中可以用，浏览器会有一些小问题
// vm.compileFunction("console.log(a)");  可以创建一个独立的函数来运行代码

// 分析模块加载的流程
// 1.加载模块 Module._load  加载模块之后 最终返回的就是module.exports
// 2.Module._resolveFilename 解析文件名， 产生一个可读取的文件名  .js? .json?
// 3.Module._cache 如果文件被缓存过 直接拿上一次的返回结果
// 4.如果模块没有加载过，会根据路径创建一个模块 new Module() {id:文件名，exports:导出结果}
// 5.缓存模块为了后续使用
// 6.module.load 加载模块（读文件）
// 7.获取扩展名来调用不同的加载方式
// 8.根据扩展名查找 对应的加载方式 Module._extension
// 9.js的模块主要是读取
// 10.读取文件后包裹函数 ， 并且传入五个参数 [ 'exports','require','module','__filename', '__dirname' ]
// 11.执行函数 用户会给module.exports 赋予值
// 12. 因为最终返回的是module.exports 所以可以拿到最终的返回结果

function Module(id) {
  this.id = id;
  this.exports = {}; // 核心的 ，每个模块的导出结果都在这里
}
Module._cache = {};
Module._extensions = {
  ".js"(module) {
    const content = fs.readFileSync(module.id, "utf8");
    let wrapperFn = vm.compileFunction(content, [
      "exports",
      "require",
      "module",
      "__filename",
      "__dirname",
    ]);
    let exports = module.exports;
    let thisValue = exports; // this就是exports
    let dirname = path.dirname(module.id);
    Reflect.apply(wrapperFn, thisValue, [
      exports,
      myRequire,
      module,
      module.id,
      dirname,
    ]); // module.exports = 'abc'
  },
  ".json"(module) {
    const content = fs.readFileSync(module.id, "utf8");
    module.exports = JSON.parse(content); // 将解析的json 直接替换导出结果
  },
};
Module._resolveFilename = function (id) {
  const fileUrl = path.resolve(__dirname, id);
  if (fs.existsSync(fileUrl)) return fileUrl;
  let exts = Reflect.ownKeys(Module._extensions);
  for (let i = 0; i < exts.length; i++) {
    const fileUrl = path.resolve(__dirname, id + exts[i]);
    if (fs.existsSync(fileUrl)) return fileUrl;
  }
  throw new Error("module not found");
};
Module.prototype.load = function (filename) {
  let ext = path.extname(filename);
  Module._extensions[ext](this); // 根据扩展名自动处理 策略模式
};
function myRequire(id) {
  // 1.解析文件名
  let filepath = Module._resolveFilename(id);
  // 2.构建模块

  let cacheModule = Module._cache[filepath];

  if (cacheModule) {
    return cacheModule.exports;
  }
  const module = new Module(filepath);
  Module._cache[filepath] = module; // 缓存当前模块
  // 3.加载模块 读取文件 + 执行操作
  module.load(filepath);
  // 4.返回结果
  return module.exports;
}

// 自己实现了一个commonjs模块加载, 读取文件，将结果赋予给module.exports 上即可
let content = require("./module.json");
console.log(content);
